04-RDS연결업그레이드

RDS 연결 웹앱 업그레이드

이전 단계에서 만든 "Hello World" 서버를 데이터베이스 연결된 실제 웹 애플리케이션으로 업그레이드합니다.

전제조건

0. 패키지 설치

# webapp 디렉토리로 이동
cd ~/webapp/nodejs-app

# npm 초기화가 안 되어 있으면 package.json 생성 
npm init -y  

# mysql2 설치 
npm install mysql2  

# express 설치 (아직 설치 안 했다면) 
npm install express

# PM2에서 앱 실행 전 확인 
ls node_modules | grep mysql2

Pasted image 20250904174825.png

1. 기존 앱 백업

기존 Hello World 서버를 백업해둡니다.

# 홈 디렉토리로 이동
cd ~

# 기존 webapp 폴더 백업
cp -r webapp/nodejs-app webapp/nodejs-app-hello-backup

# 작업할 폴더로 이동
cd webapp/nodejs-app

2. 데이터베이스 연결 앱 코드로 교체

app.js 파일 교체

기존의 간단한 웹서버를 데이터베이스 연결이 가능한 버전으로 교체합니다.

# 기존 PM2 프로세스 중지
pm2 stop webapp

# app.js 파일을 데이터베이스 연결 버전으로 교체
cat > app.js << 'EOF'
// AWS 웹앱 메인 서버 파일
// 이 파일은 웹사이트의 두뇌 역할을 합니다

// 필요한 라이브러리들을 불러옵니다
const express = require('express');    // 웹서버 만드는 도구
const mysql = require('mysql2');       // MySQL 데이터베이스 연결 도구
const path = require('path');          // 파일 경로 처리 도구

// Express 웹서버를 만듭니다
const app = express();
const port = 3000;  // 웹서버가 사용할 포트 번호

// 미들웨어 설정 (요청 처리를 위한 준비 작업)
app.use(express.json());                    // JSON 데이터를 이해할 수 있게 설정
app.use(express.static('public'));          // HTML, CSS 파일들을 서비스할 폴더 설정

console.log('웹서버를 시작하는 중입니다...');

// 환경 변수에서 데이터베이스 설정 가져오기
const dbConfig = {
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME
};

// 필수 환경 변수 검증
if (!dbConfig.host || !dbConfig.user || !dbConfig.password || !dbConfig.database) {
  console.error('필수 환경 변수가 누락되었습니다:');
  console.error('다음 환경 변수들을 모두 설정해주세요:');
  console.error('- DB_HOST: RDS 엔드포인트');
  console.error('- DB_USER: 데이터베이스 사용자명');
  console.error('- DB_PASSWORD: 데이터베이스 비밀번호');
  console.error('- DB_NAME: 데이터베이스 이름');
  console.error('');
  console.error('설정 예시:');
  console.error('export DB_HOST="your-rds-endpoint.amazonaws.com"');
  console.error('export DB_USER="admin"');
  console.error('export DB_PASSWORD="your-password"');
  console.error('export DB_NAME="webapp_db"');
  process.exit(1);
}

console.log('데이터베이스에 연결하는 중입니다...');
console.log('연결 정보:', {
  host: dbConfig.host,
  user: dbConfig.user,
  database: dbConfig.database
  // 보안상 비밀번호는 출력하지 않습니다
});

// MySQL 데이터베이스에 연결합니다
const db = mysql.createConnection(dbConfig);

// 데이터베이스 연결 및 테이블 생성
db.connect((err) => {
  if (err) {
    console.error('데이터베이스 연결 실패:', err.message);
    console.log('해결방법:');
    console.log('1. RDS 엔드포인트가 정확한지 확인하세요');
    console.log('2. RDS 보안 그룹에서 3306 포트가 열려있는지 확인하세요');
    console.log('3. 사용자명과 비밀번호가 정확한지 확인하세요');
    console.log('4. 환경 변수가 올바르게 설정되어 있는지 확인하세요');
    return;
  }

  console.log('데이터베이스 연결 성공!');

  // users 테이블을 자동으로 생성합니다 (없다면)
  const createTableQuery = `
    CREATE TABLE IF NOT EXISTS users (
      id INT AUTO_INCREMENT PRIMARY KEY,
      name VARCHAR(255) NOT NULL,
      email VARCHAR(255) NOT NULL,
      created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )
  `;

  db.query(createTableQuery, (err) => {
    if (err) {
      console.error('테이블 생성 실패:', err.message);
    } else {
      console.log('users 테이블이 준비되었습니다!');
    }
  });
});

// 메인 페이지 라우트 - 사용자가 웹사이트에 처음 접속할 때
app.get('/', (req, res) => {
  console.log('누군가 메인 페이지에 접속했습니다');
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

// 사용자 목록 조회 API - 등록된 사용자들을 보여줍니다
app.get('/users', (req, res) => {
  console.log('사용자 목록을 요청받았습니다');

  // 데이터베이스에서 모든 사용자를 최신순으로 조회
  const query = 'SELECT * FROM users ORDER BY created_at DESC';

  db.query(query, (err, results) => {
    if (err) {
      console.error('사용자 조회 실패:', err.message);
      return res.json([]); // 빈 배열을 반환 (에러가 나도 웹사이트는 동작)
    }

    console.log(`${results.length}명의 사용자를 찾았습니다`);
    res.json(results);  // 사용자 목록을 JSON 형태로 반환
  });
});

// 새 사용자 등록 API - 사용자가 등록 버튼을 눌렀을 때
app.post('/users', (req, res) => {
  const { name, email } = req.body;  // 전송받은 이름과 이메일을 추출

  console.log('새 사용자 등록 요청:', { name, email });

  // 입력값 검증 - 이름과 이메일이 비어있지 않은지 확인
  if (!name || !email) {
    console.log('이름 또는 이메일이 비어있습니다');
    return res.status(400).json({
      error: 'Name and email required',
      message: '이름과 이메일을 모두 입력해주세요'
    });
  }

  // 이름과 이메일 공백 제거
  const cleanName = name.trim();
  const cleanEmail = email.trim();

  if (!cleanName || !cleanEmail) {
    console.log('이름 또는 이메일이 공백만 포함되어 있습니다');
    return res.status(400).json({
      error: 'Name and email cannot be empty',
      message: '이름과 이메일에 내용을 입력해주세요'
    });
  }

  // 데이터베이스에 새 사용자 추가
  const query = 'INSERT INTO users (name, email) VALUES (?, ?)';

  db.query(query, [cleanName, cleanEmail], (err, result) => {
    if (err) {
      console.error('사용자 등록 실패:', err.message);
      return res.status(500).json({
        error: 'Database error',
        message: '데이터베이스 저장 중 오류가 발생했습니다'
      });
    }

    console.log(`새 사용자 등록 성공! ID: ${result.insertId}`);
    res.json({
      success: true,
      id: result.insertId,
      message: '사용자가 성공적으로 등록되었습니다',
      user: { id: result.insertId, name: cleanName, email: cleanEmail }
    });
  });
});

// 웹서버를 시작합니다!
app.listen(port, '0.0.0.0', () => {
  console.log('');
  console.log('웹서버가 성공적으로 시작되었습니다!');
  console.log(`서버 주소: http://localhost:${port}`);
  console.log(`외부 접속: http://여러분의EC2퍼블릭IP:${port}`);
  console.log('');
  console.log('서버를 중지하려면 Ctrl+C를 누르세요');
  console.log('');
});

// 프로그램이 종료될 때 데이터베이스 연결을 정리합니다
process.on('SIGINT', () => {
  console.log('서버를 종료하는 중입니다...');
  db.end(() => {
    console.log('데이터베이스 연결이 종료되었습니다');
    process.exit(0);
  });
});
EOF

3. 프론트엔드 파일 생성

사용자가 데이터를 입력하고 조회할 수 있는 웹 페이지를 만듭니다.

public 폴더 생성 및 HTML 파일 작성

# public 폴더 생성
mkdir -p public

# index.html 파일 생성
cat > public/index.html << 'EOF'
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AWS 웹앱 실습</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <header>
            <h1>AWS 클라우드 웹 애플리케이션</h1>
            <p>EC2 + RDS 연동 실습</p>
        </header>

        <main>
            <!-- 사용자 등록 섹션 -->
            <section class="form-section">
                <h2>새 사용자 등록</h2>
                <form id="userForm">
                    <div class="form-group">
                        <label for="name">이름:</label>
                        <input type="text" id="name" name="name" required>
                    </div>
                    <div class="form-group">
                        <label for="email">이메일:</label>
                        <input type="email" id="email" name="email" required>
                    </div>
                    <button type="submit">등록하기</button>
                </form>
                <div id="message"></div>
            </section>

            <!-- 사용자 목록 섹션 -->
            <section class="list-section">
                <h2>등록된 사용자 목록</h2>
                <button id="refreshBtn">목록 새로고침</button>
                <div id="userList">
                    <p>사용자 목록을 불러오는 중...</p>
                </div>
            </section>
        </main>

        <footer>
            <p>AWS EDU Week2 실습 - Node.js + MySQL</p>
        </footer>
    </div>

    <script src="script.js"></script>
</body>
</html>
EOF

CSS 스타일 파일 생성

# style.css 파일 생성
cat > public/style.css << 'EOF'
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    min-height: 100vh;
    padding: 20px;
}

.container {
    max-width: 800px;
    margin: 0 auto;
    background: white;
    border-radius: 10px;
    box-shadow: 0 10px 30px rgba(0,0,0,0.2);
    overflow: hidden;
}

header {
    background: linear-gradient(45deg, #FF6B6B, #4ECDC4);
    color: white;
    padding: 2rem;
    text-align: center;
}

header h1 {
    font-size: 2.5rem;
    margin-bottom: 0.5rem;
}

header p {
    font-size: 1.1rem;
    opacity: 0.9;
}

main {
    padding: 2rem;
}

.form-section, .list-section {
    margin-bottom: 3rem;
    padding: 1.5rem;
    background: #f8f9fa;
    border-radius: 8px;
    border-left: 4px solid #4ECDC4;
}

.form-section h2, .list-section h2 {
    color: #333;
    margin-bottom: 1.5rem;
    font-size: 1.5rem;
}

.form-group {
    margin-bottom: 1rem;
}

label {
    display: block;
    margin-bottom: 0.5rem;
    color: #555;
    font-weight: 500;
}

input[type="text"], input[type="email"] {
    width: 100%;
    padding: 0.75rem;
    border: 1px solid #ddd;
    border-radius: 4px;
    font-size: 1rem;
    transition: border-color 0.3s;
}

input[type="text"]:focus, input[type="email"]:focus {
    outline: none;
    border-color: #4ECDC4;
    box-shadow: 0 0 0 2px rgba(78, 205, 196, 0.2);
}

button {
    background: linear-gradient(45deg, #FF6B6B, #4ECDC4);
    color: white;
    padding: 0.75rem 2rem;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 1rem;
    font-weight: 500;
    transition: transform 0.2s, box-shadow 0.2s;
}

button:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}

button:active {
    transform: translateY(0);
}

#refreshBtn {
    background: linear-gradient(45deg, #4ECDC4, #667eea);
    margin-bottom: 1rem;
}

#message {
    margin-top: 1rem;
    padding: 0.75rem;
    border-radius: 4px;
    display: none;
}

#message.success {
    background-color: #d4edda;
    color: #155724;
    border: 1px solid #c3e6cb;
}

#message.error {
    background-color: #f8d7da;
    color: #721c24;
    border: 1px solid #f5c6cb;
}

.user-item {
    background: white;
    padding: 1rem;
    margin-bottom: 0.5rem;
    border-radius: 4px;
    border-left: 3px solid #4ECDC4;
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.user-item h3 {
    color: #333;
    margin-bottom: 0.5rem;
}

.user-item p {
    color: #666;
    margin-bottom: 0.25rem;
}

.user-item small {
    color: #999;
}

footer {
    background: #333;
    color: white;
    text-align: center;
    padding: 1rem;
}

.loading {
    text-align: center;
    color: #666;
    font-style: italic;
}

@media (max-width: 600px) {
    .container {
        margin: 0;
        border-radius: 0;
    }
    
    header {
        padding: 1.5rem;
    }
    
    header h1 {
        font-size: 2rem;
    }
    
    main {
        padding: 1rem;
    }
}
EOF

JavaScript 파일 생성

# script.js 파일 생성
cat > public/script.js << 'EOF'
// 페이지가 로드되면 실행되는 함수
document.addEventListener('DOMContentLoaded', function() {
    const userForm = document.getElementById('userForm');
    const messageDiv = document.getElementById('message');
    const userListDiv = document.getElementById('userList');
    const refreshBtn = document.getElementById('refreshBtn');

    // 초기 사용자 목록 로드
    loadUsers();

    // 사용자 등록 폼 제출 처리
    userForm.addEventListener('submit', function(e) {
        e.preventDefault();
        
        const name = document.getElementById('name').value.trim();
        const email = document.getElementById('email').value.trim();
        
        if (!name || !email) {
            showMessage('이름과 이메일을 모두 입력해주세요.', 'error');
            return;
        }
        
        // 서버에 사용자 등록 요청
        fetch('/users', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ name: name, email: email })
        })
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                showMessage(data.message, 'success');
                userForm.reset(); // 폼 초기화
                loadUsers(); // 사용자 목록 새로고침
            } else {
                showMessage(data.message || '등록 중 오류가 발생했습니다.', 'error');
            }
        })
        .catch(error => {
            console.error('Error:', error);
            showMessage('서버와의 통신 중 오류가 발생했습니다.', 'error');
        });
    });

    // 새로고침 버튼 클릭 처리
    refreshBtn.addEventListener('click', loadUsers);

    // 사용자 목록을 서버에서 가져와 화면에 표시
    function loadUsers() {
        userListDiv.innerHTML = '<p class="loading">사용자 목록을 불러오는 중...</p>';
        
        fetch('/users')
        .then(response => response.json())
        .then(users => {
            if (users.length === 0) {
                userListDiv.innerHTML = '<p>등록된 사용자가 없습니다.</p>';
                return;
            }
            
            const userHTML = users.map(user => `
                <div class="user-item">
                    <h3>${escapeHtml(user.name)}</h3>
                    <p>이메일: ${escapeHtml(user.email)}</p>
                    <small>등록일: ${new Date(user.created_at).toLocaleDateString('ko-KR', {
                        year: 'numeric',
                        month: 'long',
                        day: 'numeric',
                        hour: '2-digit',
                        minute: '2-digit'
                    })}</small>
                </div>
            `).join('');
            
            userListDiv.innerHTML = userHTML;
        })
        .catch(error => {
            console.error('Error:', error);
            userListDiv.innerHTML = '<p>사용자 목록을 불러오는데 실패했습니다.</p>';
        });
    }

    // 메시지 표시 함수
    function showMessage(message, type) {
        messageDiv.textContent = message;
        messageDiv.className = type;
        messageDiv.style.display = 'block';
        
        // 3초 후 메시지 자동 숨김
        setTimeout(() => {
            messageDiv.style.display = 'none';
        }, 3000);
    }

    // XSS 방지를 위한 HTML 이스케이프 함수
    function escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }
});
EOF

4. 환경 변수 설정

데이터베이스 연결 정보를 환경 변수로 설정합니다.

# RDS 인스턴스 정보를 환경 변수로 설정
# 여러분의 실제 RDS 엔드포인트와 비밀번호를 입력하세요!
export DB_HOST="your-rds-endpoint.cx6mc24eklg5.ap-northeast-3.rds.amazonaws.com"
export DB_USER="admin"
export DB_PASSWORD="your-password"
export DB_NAME="webapp_db"

# 설정 확인
echo "DB_HOST: $DB_HOST"
echo "DB_USER: $DB_USER"
echo "DB_NAME: $DB_NAME"

중요: 실제 RDS 엔드포인트와 설정한 비밀번호로 교체해야 합니다!

5. 애플리케이션 재시작

업그레이드된 앱을 실행합니다.

# 기존 프로세스 완전히 중지 및 삭제
pm2 delete webapp

# 새로운 앱으로 시작
pm2 start app.js --name webapp

# 상태 확인
pm2 status

# 로그 확인 (연결 상태 모니터링)
pm2 logs webapp

6. 웹 애플리케이션 테스트

브라우저 접속 테스트

  1. 웹 브라우저를 열고 접속:
    http://[EC2-Public-IP]:3000

  2. 데이터베이스 연결 확인:

    • PM2 로그에서 "데이터베이스 연결 성공!" 메시지 확인
    • "users 테이블이 준비되었습니다!" 메시지 확인

기능 테스트

  1. 사용자 등록 테스트:

    • 이름: 홍길동
    • 이메일: hong@test.com
    • "등록하기" 버튼 클릭
    • 성공 메시지 확인
  2. 사용자 목록 조회:

    • 등록한 사용자가 목록에 표시되는지 확인
    • "목록 새로고침" 버튼 동작 확인

7. 연결 상태 확인

터미널에서 직접 데이터베이스 확인

# MySQL 클라이언트로 직접 연결하여 데이터 확인
mysql -h $DB_HOST -u $DB_USER -p$DB_PASSWORD $DB_NAME

# MySQL에 접속되면 다음 명령어 실행:
# SELECT * FROM users;
# quit

로그 모니터링

# 실시간 로그 확인
pm2 logs webapp --lines 50

# 사용자 등록/조회 시 로그 메시지 확인:
# - "새 사용자 등록 요청"
# - "새 사용자 등록 성공"
# - "사용자 목록을 요청받았습니다"

완료 체크리스트


**이제 EC2에서 실행되는 Node.js 웹 애플리케이션이 RDS MySQL 데이터베이스와 성공적으로 연결되었습니다.


관련 문서: AWS EDU/Archive/조선대학교 AWS 멘토링/Week2-Dynamic-WebApp-Deployment/Week2-전체가이드, AWS EDU/Archive/조선대학교 AWS 멘토링/Week2-Dynamic-WebApp-Deployment/03-기본실습-NodeJS/02-NodeJS설치배포, AWS EDU/Archive/조선대학교 AWS 멘토링/Week2-Dynamic-WebApp-Deployment/03-기본실습-NodeJS/03-RDS생성